/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package io.iec.edp.caf.autoconfigure.reader;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.system.ApplicationHome;
import org.yaml.snakeyaml.LoaderOptions;
import org.yaml.snakeyaml.Yaml;

import java.io.*;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Stream;

/**
 * This is {@link YamlCheckUtil}.
 *
 * @author HuTianyue
 * @since 1.1.0
 */
@Slf4j
public class YamlCheckUtil {

    //runtime目录
    private static String serverRTPath;

    //基目录
    private static String basePath;

    //Base盘名称 支持从配置中获取
    final private static String SERVER_RUNTIME_PATH_NAME = "jstack";

    //后台启动目录
    private static String startupPath;

    //Base盘名称 支持从配置中获取
    final private static int SERVER_RUNTIME_PATH_DEPTH = 5;

    private final static Yaml yaml = new Yaml(new LoaderOptions() {{
        setAllowDuplicateKeys(true);
    }});

    /**
     * 返回运行时目录名称
     * @return -
     */
    public static String getServerRuntimePathName() {
        return System.getProperty("server.runtime.path.name", SERVER_RUNTIME_PATH_NAME);
    }

    /**
     * 获取盘目录
     *
     * @return -
     */
    public static String getBasePath() {
        if (basePath == null) {
            File file = new File(getServerRTPath());
            basePath = file.getParentFile().getAbsolutePath().replaceAll("\\\\", "/");
        }

        return basePath;
    }
    /**
     * 获取主应用目录(jstack)
     *
     * @return -
     */
    public static String getServerRTPath() {
        if (serverRTPath == null) {
            String nowDir = getStartupPath();
            File file = new File(nowDir);
            String targetName = getServerRuntimePathName();

            //先找自定义目录 再找jstack
            serverRTPath = doGetServerRTPath(file, targetName);
            if (serverRTPath == null && !SERVER_RUNTIME_PATH_NAME.equals(targetName)) {
                serverRTPath = doGetServerRTPath(file, SERVER_RUNTIME_PATH_NAME);
            }

            if (serverRTPath == null) {
                throw new RuntimeException("Server runtime path not found");
            }
        }

        return serverRTPath;
    }

    /**
     * 获取启动类所在的目录(runtime)
     *
     * @return -
     */
    public static String getStartupPath() {
        if (startupPath == null) {
            ApplicationHome home = new ApplicationHome(getMainClass());
            // returns the folder where the jar is. This is what I wanted.
            startupPath = home.getDir().getAbsolutePath().replaceAll("\\\\", "/");
        }

        return startupPath;
    }

    /**
     * 获取根启动类(PropertiesLaunched)
     *
     * @return -
     */
    private static Class<?> getMainClass() {
        try {
            StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
            for (int i = stackTrace.length - 1; i >= 0; i--) {
                StackTraceElement stackTraceElement = stackTrace[i];
                if ("main".equals(stackTraceElement.getMethodName())) {
                    return Class.forName(stackTraceElement.getClassName());
                }
            }
        } catch (ClassNotFoundException ex) {
        }

        return null;
    }

    /**
     * 递归查找运行时目录
     * @param file
     * @param targetName
     * @return -
     */
    private static String doGetServerRTPath(File file, String targetName) {
        int depth = getServerRuntimePathDepth();
        while (depth > 0 && file != null) {
            for (File nowFile : Objects.requireNonNull(file.listFiles())) {
                if (nowFile.isDirectory() && nowFile.getName().equals(targetName)) {
                    serverRTPath = nowFile.getPath().replaceAll("\\\\", "/");
                    return serverRTPath;
                }
            }

            file = file.getParentFile();
            depth--;
        }

        return null;
    }

    /**
     * 返回层级深度
     * @return -
     */
    private static Integer getServerRuntimePathDepth() {
        String depth = System.getProperty("server.runtime.path.depth", String.valueOf(SERVER_RUNTIME_PATH_DEPTH));
        return Integer.parseInt(depth);
    }


    public static String getHashCode() {
        String lastBackupHashcodePath = Paths.get(
                getBasePath(),
                getServerRuntimePathName(),
                "var",
                "cloud-config",
                "last-backup-hashcode",
                "hashCode.yaml"
        ).toString();
        File lastBackupHashcodeFile = new File(lastBackupHashcodePath);
        if (lastBackupHashcodeFile.exists()) {
            String hashCode = readFile(lastBackupHashcodeFile);
            return hashCode;
        } else {
            return null;
        }
    }

    public static File getApplicationYamlFile() {
        String applicationYamlPath = Paths.get(
                getBasePath(),
                getServerRuntimePathName(),
                "runtime",
                "application.yaml"
        ).toString();
        File applicationYamlFile = new File(applicationYamlPath);
        return applicationYamlFile;
    }

    public static String readFile(File file) {
        StringBuilder contentBuilder = new StringBuilder();
        try (Stream<String> stream = Files.lines(Paths.get(file.getAbsolutePath()), StandardCharsets.UTF_8)) {
            stream.forEach(s -> contentBuilder.append(s));
        } catch (IOException e) {
            log.error(e.getMessage(),e);
        }
        return contentBuilder.toString();
    }

    /**
          * 获取文件的md5值
          * @param filePath 文件路径
          * @return
          */
    public static String md5HashCodeFile(String filePath) {
        try (FileInputStream fis = new FileInputStream(filePath);) {
            //拿到一个MD5转换器,如果想使用SHA-1或SHA-256，则传入SHA-1,SHA-256
            MessageDigest md = MessageDigest.getInstance("MD5");
            //分多次将一个文件读入
            byte[] buffer = new byte[1024];
            int length = -1;
            while ((length = fis.read(buffer, 0, 1024)) != -1) {
                md.update(buffer, 0, length);
            }
            fis.close();
            //转换并返回包含16个元素字节数组,返回数值范围为-128到127
            byte[] md5Bytes = md.digest();
            BigInteger bigInt = new BigInteger(1, md5Bytes);//1代表绝对值
            return bigInt.toString(16);//转换为16进制
        } catch (Exception e) {
            log.error(e.getMessage(),e);
            return "";
        }
    }

    public static File getConfigSettingYamlFile() {
        String configSettingYamlPath = Paths.get(
                getBasePath(),
                getServerRuntimePathName(),
                "config",
                "config-setting.yml"
        ).toString();
        File configSettingYamlFile = new File(configSettingYamlPath);
        return configSettingYamlFile;
    }

    /**
     * 判断是否启用emc
     * @return
     */
    public static boolean emcEnabled(){
        File configSettingYamlFile = getConfigSettingYamlFile();
        if(configSettingYamlFile.exists()){
            try (InputStream yamlInputStream = new FileInputStream(configSettingYamlFile)){
                Map<String, Object> configSettingMap = yaml.loadAs(yamlInputStream,Map.class);
                boolean enabled = (boolean)(((Map<String, Object>)configSettingMap.get("cloud-config")).get("enabled"));
                if(enabled){
                    return true;
                }
            } catch (FileNotFoundException e) {
                log.error(e.getMessage(),e);
            } catch (IOException e) {
                log.error(e.getMessage(),e);
            } catch (Exception e) {
                log.error(e.getMessage(),e);
            }
        }
        return false;
    }

    /**
     * 判断yaml文件是否被修改
     * true为被修改
     * @return
     */
    public static boolean applicationYamlContentChanged() {
        if(emcEnabled()){
            File applicationYamlFile = getApplicationYamlFile();
            if (applicationYamlFile.exists()) {
                String hashCode = getHashCode();
                String md5Hashcode = md5HashCodeFile(applicationYamlFile.getAbsolutePath());
                if(hashCode==null||md5Hashcode.equals(hashCode)){
                    return false;
                }else{
                    return true;
                }
            }else {
                return false;
            }
        }else{
            return false;
        }
    }

}
